package com.yfqy.app.security.login.jscode;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.yfqy.app.configure.Idgenerator.SnowflakeIdGenerator;
import com.yfqy.app.configure.http.HttpUtils;
import com.yfqy.app.domain.entity.Customer;
import com.yfqy.app.domain.mapper.CustomerMapper;
import com.yfqy.app.enums.ErrorCode;
import com.yfqy.app.enums.LoginType;
import com.yfqy.app.exception.BusinessException;
import com.yfqy.app.security.login.LoginUserInfo;
import com.yfqy.app.util.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class JSCodeAuthenticationProvider implements AuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(JSCodeAuthenticationProvider.class);

    // 微信API常量
    private static final String WX_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
    private static final String GRANT_TYPE = "authorization_code";
    private static final String OPENID_KEY = "openid";
    private static final String UNIONID_KEY = "unionid";
    private static final String SESSION_KEY = "session_key";
    private static final String ERRCODE_KEY = "errcode";
    private static final String ERRMSG_KEY = "errmsg";

    // 错误缓存配置
    private static final long ERROR_CACHE_EXPIRE_TIME = 5; // 5分钟
    private static final Cache<String, Long> ERROR_CODE_CACHE = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(ERROR_CACHE_EXPIRE_TIME, TimeUnit.MINUTES)
            .build();

    @Value("${wechat.pay.app-id:}")
    private String appid;

    @Value("${wechat.pay.secret:}")
    private String secret;

    @Autowired
    private HttpUtils httpUtils;

    @Autowired
    private CustomerMapper customerMapper;

    @Autowired
    private SnowflakeIdGenerator idGenerator;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 参数校验
        validateConfig();

        JSCodeAuthentication jsCodeAuthentication = (JSCodeAuthentication) authentication;

        String jsCode = jsCodeAuthentication.getJs_code();

        try {
            // 检查错误缓存
            if (isInErrorCache(jsCode)) {
                logger.warn("请求过于频繁，jsCode: {}", jsCode);
                throw new BusinessException(ErrorCode.REQUEST_TOO_FREQUENT);
            }

            // 构建请求URL
            String requestUrl = buildWxRequestUrl(jsCode);

            // 调用微信API
            String result1 = httpUtils.doGet(requestUrl);
            String result = "{\n" +
                    "\"openid\":\"oGi0L45Sp8ADmGYnXtxH446YAXZo\",\n" +
                    "\"session_key\":\"xxxxx\",\n" +
                    "\"unionid\":\"xxxxx\",\n" +
                    "\"errcode\":0,\n" +
                    "\"errmsg\":\"xxxxx\"\n" +
                    "}";
            Map<String, Object> responseMap = JSON.parse(result, Map.class);

            // 处理微信API响应
            handleWxResponseError(responseMap, jsCode);

            // 获取openid并验证
            String openid = getOpenidFromResponse(responseMap);
            String unionid = getUnionidFromResponse(responseMap);
            String sessionKey = getSessionKeyFromResponse(responseMap);
            Long regFromId = jsCodeAuthentication.getRegFromId();

            // 处理用户信息
            Customer customer = processCustomer(openid, unionid, regFromId, sessionKey);

            // 构建认证结果
            return buildAuthenticationResult(jsCodeAuthentication, customer, sessionKey);

        } catch (BusinessException e) {
            // 业务异常加入错误缓存
            addToErrorCache(jsCode);
            throw e;
        } catch (IOException e) {
            logger.error("微信API调用失败: {}", e.getMessage(), e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR.getCode(), "网络请求失败");
        } catch (Exception e) {
            logger.error("微信登录认证未知错误: {}", e.getMessage(), e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 添加到错误缓存
     */
    private void addToErrorCache(String jsCode) {
        ERROR_CODE_CACHE.put(jsCode, System.currentTimeMillis());
        logger.debug("添加到错误缓存: {}", jsCode);
    }

    /**
     * 检查是否在错误缓存中
     */
    private boolean isInErrorCache(String jsCode) {
        return ERROR_CODE_CACHE.getIfPresent(jsCode) != null;
    }

    /**
     * 从错误缓存中移除
     */
    private void removeFromErrorCache(String jsCode) {
        ERROR_CODE_CACHE.invalidate(jsCode);
        logger.debug("从错误缓存移除: {}", jsCode);
    }

    /**
     * 清空错误缓存
     */
    public void clearErrorCache() {
        ERROR_CODE_CACHE.invalidateAll();
        logger.info("错误缓存已清空");
    }

    /**
     * 清理过期缓存
     */
    public void cleanUpErrorCache() {
        ERROR_CODE_CACHE.cleanUp();
        logger.debug("错误缓存清理完成");
    }

    /**
     * 获取错误缓存大小
     */
    public long getErrorCacheSize() {
        return ERROR_CODE_CACHE.estimatedSize();
    }

    /**
     * 验证配置参数
     */
    private void validateConfig() {
        if (StringUtils.isBlank(appid) || StringUtils.isBlank(secret)) {
            logger.error("微信小程序配置缺失: appid={}, secret={}", appid, secret);
            throw new BusinessException(ErrorCode.WECHAT_CONFIG_ERROR);
        }
    }


    /**
     * 构建微信请求URL
     */
    private String buildWxRequestUrl(String jsCode) {
        return String.format("%s?appid=%s&secret=%s&js_code=%s&grant_type=%s",
                WX_LOGIN_URL, appid, secret, jsCode, GRANT_TYPE);
    }

    /**
     * 处理微信API错误响应
     */
    private void handleWxResponseError(Map<String, Object> responseMap, String jsCode) {
        if (responseMap.containsKey(ERRCODE_KEY)) {
            Integer errcode = (Integer) responseMap.get(ERRCODE_KEY);
            String errmsg = (String) responseMap.get(ERRMSG_KEY);

            if (errcode != null && errcode != 0) {
                logger.warn("微信API返回错误: errcode={}, errmsg={}, jsCode={}", errcode, errmsg, jsCode);

                // 将错误请求加入缓存
                addToErrorCache(jsCode);

                throw new BusinessException(ErrorCode.WECHAT_API_ERROR.getCode(),
                        String.format("微信API错误: %s(%d)", errmsg, errcode));
            }
        }
    }

    /**
     * 从响应中获取openid
     */
    private String getOpenidFromResponse(Map<String, Object> responseMap) {
        String openid = (String) responseMap.get(OPENID_KEY);
        if (StringUtils.isBlank(openid)) {
            logger.error("微信API返回openid为空: {}", responseMap);
            throw new BusinessException(ErrorCode.WECHAT_API_ERROR.getCode(), "获取openid失败");
        }
        return openid;
    }

    /**
     * 从响应中获取unionid
     */
    private String getUnionidFromResponse(Map<String, Object> responseMap) {
        return (String) responseMap.get(UNIONID_KEY);
    }

    /**
     * 从响应中获取session_key
     */
    private String getSessionKeyFromResponse(Map<String, Object> responseMap) {
        return (String) responseMap.get(SESSION_KEY);
    }

    /**
     * 处理用户信息
     */
    private Customer processCustomer(String openid, String unionid,
                                     Long regFromId, String sessionKey) {
        Customer customer = customerMapper.selectOneByOpenId(openid);

        if (customer == null) {
            // 新用户注册
            logger.info("新用户登录 openid={}", openid);
            customer = createNewCustomer(openid, unionid, regFromId, sessionKey);
        } else {
            // 更新现有用户信息
            updateExistingCustomer(customer, unionid, sessionKey);
        }

        return customer;
    }

    /**
     * 创建新用户
     */
    private Customer createNewCustomer(String openid, String unionid,
                                       Long regFromId, String sessionKey) {
        Customer customer = new Customer();
        customer.setId(idGenerator.nextId());
        customer.setOpenid(openid);
        customer.setUnionid(unionid);
        customer.setRegisterSourceId(regFromId);
        customer.setSessionKey(sessionKey);
        customer.setAvatarUrl("");
        // 设置其他默认信息
        customer.setNickname("微信用户");

        customerMapper.insert(customer);
        return customer;
    }

    /**
     * 更新现有用户
     */
    private void updateExistingCustomer(Customer customer, String unionid, String sessionKey) {
        boolean needUpdate = false;

        // 更新unionid（如果之前没有）
        if (StringUtils.isNotBlank(unionid) && StringUtils.isBlank(customer.getUnionid())) {
            customer.setUnionid(unionid);
            needUpdate = true;
        }

        // 更新session_key
        if (StringUtils.isNotBlank(sessionKey)) {
            customer.setSessionKey(sessionKey);
            needUpdate = true;
        }

        // 如果有更新才执行数据库操作
        if (needUpdate) {
            customerMapper.updateById(customer);
        }
    }

    /**
     * 构建认证结果
     */
    private Authentication buildAuthenticationResult(JSCodeAuthentication authentication,
                                                     Customer customer,
                                                     String sessionKey) {
        LoginUserInfo loginUserInfo = BeanUtil.toBean(customer, LoginUserInfo.class);
        loginUserInfo.setLoginType(LoginType.JSCODE);
        loginUserInfo.setRegFromId(customer.getRegisterSourceId());
        loginUserInfo.setSessionKey(sessionKey); // 保存session_key
        loginUserInfo.setOpenid(customer.getOpenid());
        authentication.setCurrentUser(loginUserInfo);
        authentication.setAuthenticated(true);

        // 认证成功时，从错误缓存中移除（如果有的话）
        removeFromErrorCache(authentication.getJs_code());

        return authentication;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JSCodeAuthentication.class.isAssignableFrom(authentication);
    }
}